package xj.graph2d;

import static xj.graph2d.DrawShapeConstants.EDGE_LABEL_ROLES;
import static xj.graph2d.DrawShapeConstants.HOTSPOT_EDGE_HEAD_LABEL;
import static xj.graph2d.DrawShapeConstants.HOTSPOT_EDGE_LABEL;
import static xj.graph2d.DrawShapeConstants.HOTSPOT_EDGE_TAIL_LABEL;
import static xj.graph2d.DrawShapeConstants.HOTSPOT_NONE;
import static xj.graph2d.DrawShapeConstants.HOTSPOT_SIZE;
import static xj.graph2d.DrawShapeConstants.ROLE_EDGE_HEAD_LABEL;
import static xj.graph2d.DrawShapeConstants.ROLE_EDGE_HEAD_LABEL2;
import static xj.graph2d.DrawShapeConstants.ROLE_EDGE_LABEL;
import static xj.graph2d.DrawShapeConstants.ROLE_EDGE_TAIL_LABEL;
import static xj.graph2d.DrawShapeConstants.ROLE_EDGE_TAIL_LABEL2;
import static xj.util.xml.XMLUtil.getXMLAttributeString;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.io.PrintWriter;

import xj.graph2d.graph.AbstractGraphEdge;
import xj.graph2d.graph.Connector;
import xj.graph2d.graph.Geometry;
import xj.graph2d.graph.GraphConstants;
import xj.graph2d.graph.GraphEdge;
import xj.graph2d.graph.GraphNode;
import xj.graph2d.util.DrawingUtilities;
import xj.lang.CodeWriter;
import xj.util.gui.Point2DFloat;

/**
 * Graph edge 
 *   source, tail, p1 
 *   destination, head, p2
 */
public abstract class GraphEdgeBase 
  extends GraphComponentShape 
  implements GraphEdge<GraphNodeBase> {

  protected GraphEdgeBase(boolean createComp) {
    super(createComp ? GraphConstants.EDGE : -1);
    setSymetricResize(false);
  }

  protected GraphEdgeBase() {
    super(GraphConstants.EDGE);
    setSymetricResize(false);
  }

  protected GraphEdgeBase(GraphNodeBase src, GraphNodeBase des) {
    super(GraphConstants.EDGE);
    setSource(src);
    setDestination(des);
    setSymetricResize(false);
  }

  protected GraphEdgeBase(GraphNodeBase src, GraphNodeBase des, int edgeType) {
    super(GraphConstants.EDGE);
    setSource(src);
    setDestination(des);
    setEdgeType(edgeType);
    setSymetricResize(false);
  }

  @Override
  public void initComponent(String compid) {
    comp = new AbstractGraphEdge();
    if (compid != null) {
      comp.setId(compid);
    }
  }

  @Override
  public void copyTo(DrawShapeBase s)
    throws CloneNotSupportedException {
    super.copyTo(s);
    if (s instanceof GraphEdgeBase) {
      GraphEdgeBase e = (GraphEdgeBase) s;
      e.setSource(getSource());
      e.setDestination(getDestination());
      if (headOffset != null) {
	e.headOffset = new Offset(headOffset);
      } else {
	e.headOffset = null;
      }
      if (tailOffset != null) {
	e.tailOffset = new Offset(tailOffset);
      } else {
	e.tailOffset = null;
      }
      e.showHotSpot = showHotSpot;
      e.allowSelfLoop = allowSelfLoop;
      e.allowDangling = allowDangling;
      e.edgeLayoutPolicy = edgeLayoutPolicy;

      e.setEdgeType(getEdgeType());
    }
  }

  public int getType() {
    return GraphConstants.EDGE;
  }

  @Override
  public boolean isEdge() {
    return true;
  }

  public GraphNodeBase getSource() {
    if (comp instanceof GraphEdge) {
      return ((GraphEdge<GraphNodeBase>) comp).getSource();
    }
    return null;
  }

  public GraphNodeBase getDestination() {
    if (comp instanceof GraphEdge) {
      return ((GraphEdge<GraphNodeBase>) comp).getDestination();
    }
    return null;
  }

  public void setSource(GraphNodeBase node) {
    if (comp instanceof GraphEdge) {
      ((GraphEdge<GraphNodeBase>) comp).setSource(node);
    }
  }

  public void setDestination(GraphNodeBase node) {
    if (comp instanceof GraphEdge) {
      ((GraphEdge<GraphNodeBase>) comp).setDestination(node);
    }
  }

  public String getSourcePort() {
    if (comp instanceof GraphEdge) {
      return ((GraphEdge<GraphNodeBase>) comp).getSourcePort();
    }
    return null;
  }

  public String getDestinationPort() {
    if (comp instanceof GraphEdge) {
      return ((GraphEdge<GraphNodeBase>) comp).getDestinationPort();
    }
    return null;
  }

  public void setSourcePort(String port) {
    if (comp instanceof GraphEdge) {
      ((GraphEdge<GraphNodeBase>) comp).setSourcePort(port);
    }
  }

  public void setDestinationPort(String port) {
    if (comp instanceof GraphEdge) {
      ((GraphEdge<GraphNodeBase>) comp).setDestinationPort(port);
    }
  }

  public void setSourceConnector(Connector c) {
    if (comp instanceof GraphEdge) {
      ((GraphEdge<GraphNodeBase>) comp).setSourceConnector(c);
    }
  }

  public Connector getSourceConnector() {
    if (comp instanceof GraphEdge) {
      return ((GraphEdge<GraphNodeBase>) comp).getSourceConnector();
    }
    return null;
  }

  public void setDestinationConnector(Connector c) {
    if (comp instanceof GraphEdge) {
      ((GraphEdge<GraphNodeBase>) comp).setDestinationConnector(c);
    }
  }

  public Connector getDestinationConnector() {
    if (comp instanceof GraphEdge) {
      return ((GraphEdge<GraphNodeBase>) comp).getDestinationConnector();
    }
    return null;
  }

  public boolean isHyperEdge() {
    return (comp instanceof GraphEdge && 
	    ((GraphEdge<GraphNodeBase>) comp).isHyperEdge());
  }

  public int getEdgeType() {
    if (comp instanceof GraphEdge) {
      return ((GraphEdge<GraphNodeBase>) comp).getEdgeType();
    } else {
      return 0;
    }
  }

  public void setEdgeType(int type) {
    if (comp instanceof GraphEdge) {
      ((GraphEdge<GraphNodeBase>) comp).setEdgeType(type);
    }
  }

  @Override
  public double getWeight() {
    if (comp != null) {
      return comp.getWeight();
    }
    return 1.0;
  }

  @Override
  public void setWeight(double weight) {
    if (comp != null) {
      comp.setWeight(weight);
    }
  }

  public boolean isDirected() {
    if (comp instanceof GraphEdge) {
      return ((GraphEdge<GraphNodeBase>) comp).isDirected();
    }
    return true;
  }

  public void setDirected(boolean directed) {
    if (comp instanceof GraphEdge) {
      ((GraphEdge<GraphNodeBase>) comp).setDirected(directed);
    }
  }

  public String getEdgeString() {
    if (comp instanceof GraphEdge) {
      return ((GraphEdge<GraphNodeBase>) comp).getEdgeString();
    }
    return null;
  }

  @Override
  public Point2D getEnd1() {
    return p1;
  }

  @Override
  public Point2D getEnd2() {
    return p2;
  }

  static double NEAR_END_DISTANCE = 20; 

  public boolean isNearEnd1(float x, float y) { 
    if (p1 != null) { 
      double d = Geometry.distance(p1.getX(), p1.getY(), x, y); 
      if (d <= NEAR_END_DISTANCE) { 
	return true;
      }
    }
    return false; 
  }

  public boolean isNearEnd2(float x, float y) { 
    if (p2 != null) { 
      double d = Geometry.distance(p2.getX(), p2.getY(), x, y); 
      if (d <= NEAR_END_DISTANCE) { 
	return true;
      }
    }
    return false; 
  }

  public boolean isSelfLoop() {
    GraphNodeBase src = getSource();
    GraphNodeBase des = getDestination();
    return (src != null && src == des);
  }

  @Override
  public void doLayout() {
    if (debug) System.out.println("GraphEdgeBase.doLayout() " + getId());

    adjustEnds();
    //adjustEdge();
    layoutComponents();
  }

  public void adjustEdge() { 
    
  }

  public boolean isFlippable() {
    return false;
  } 

  public void flipOrientation() {} 

  @Override
  public void layoutComponents() {
    if (debug) System.out.println("GraphEdgeBase.layoutComponents() " + getId());

    adjustLabels();
  }

  public boolean isAllowSelfLoop() {
    return allowSelfLoop;
  }

  public void setAllowSelfLoop(boolean loop) {
    allowSelfLoop = loop;
  }

  public void adjustSelfLoop() {
  }

  public boolean isAllowDangling() {
    return allowDangling;
  }

  public void setAllowDangling(boolean dangling) {
    allowDangling = dangling;
  }

  public void adjustEnds() {
    adjustEnds(true);
  }

  public void adjustEnds(boolean adjustCtrPts) {
    GraphNodeBase src = getSource();
    GraphNodeBase des = getDestination();
    adjustEnds(src, des, adjustCtrPts);
  }

  public void adjustEnds(GraphNodeBase src, 
			 GraphNodeBase des, 
			 boolean adjustCtrPts) {
    if (debug) {
      System.out.println("GraphEdgeBase.adjustEnds() " + getId() + 
			 "  src=" + src + "  des=" + des);
    }

    float headOffsetX = 0f, headOffsetY = 0f, tailOffsetX = 0f, tailOffsetY = 0f;
    if (des != null && des.isAllowEdgeOffset()) {
      headOffsetX = headOffset.dx * des.getShapeWidth();
      headOffsetY = headOffset.dy * des.getShapeHeight();
    }
    if (src != null && src.isAllowEdgeOffset()) {
      tailOffsetX = tailOffset.dx * src.getShapeWidth();
      tailOffsetY = tailOffset.dy * src.getShapeHeight();
    }

    Point2D q1, q2, p, c;
    if (src != null) {
      if (des != null) {
	c = des.getCenter();
	p = new Point2DFloat((float) c.getX() + headOffsetX, 
			     (float) c.getY() + headOffsetY);
      } else {
	p = p2;
      }
      q1 = src.intersect(p, // p2,
			 tailOffsetX, tailOffsetY);
    } else {
      q1 = p1;
    }
    if (des != null) {
      if (src != null) {
	c = src.getCenter();
	p = new Point2DFloat((float) c.getX() + tailOffsetX, 
			     (float) c.getY() + tailOffsetY);
      } else {
	p = p1;
      }
      q2 = des.intersect(p, // p1,
			 headOffsetX, headOffsetY);
    } else {
      q2 = p2;
    }

    if (q1 != null && q2 != null) {
      setEnds((float) q1.getX(), (float) q1.getY(), (float) q2.getX(), (float) q2.getY());
    }
  }

  @Override
  public Label removeLabel(int role) {
    Label label = findLabel(role);
    if (label != null) {
      removeLabel(label);
    }
    return label;
  }

  public String getEdgeDisplayName() { 
    return getLabel();
  }

  public void removeLabel(Label label) {
    if (label != null) {
      label.setOffset(0, 0);
      removeShape(label);
    }
  }

  public String getLabel() {
    return getLabel(ROLE_EDGE_LABEL);
  }

  @Override
  public Label getLabelShape() {
    return getLabelShape(ROLE_EDGE_LABEL);
  }

  public void setLabel(String label) {
    setLabel(ROLE_EDGE_LABEL, label);
  }

  @Override
  public void setLabel(Label label) {
    setLabel(ROLE_EDGE_LABEL, label);
  }

  public void setLabel(int role, String labelText) {
    Label label = findLabel(role);
    if (label == null) {
      label = new TextShape(labelText);
      label.setRole(role);
      addShape(label);
    } else {
      label.setText(labelText);
    }
    positionLabel(role);
  }

  protected Offset calculateLabelOffset(int role) {
    Label label = findLabel(role);
    if (label != null) {
      return calculateLabelOffset(role, label);
    } else {
      return null;
    }
  }

  /**
   * Calculate the normal (unadjusted) offset of the label of the
   * specified role. Reference point: Label: center Edge: depends on role
   * Edge: mid-point Head: p2 Tail: p1
   * 
   */
  protected Offset calculateLabelOffset(int role, Label label) {
    float x = (float) bounds.getX();
    float y = (float) bounds.getY();
    float w = (float) bounds.getWidth();
    float h = (float) bounds.getHeight();
    float px1 = (float) p1.getX();
    float py1 = (float) p1.getY();
    float px2 = (float) p2.getX();
    float py2 = (float) p2.getY();
    float tw = 0;
    float th = 10;
    float ypad = 3;
    if (label != null) {
      tw = label.getWidth();
      th = label.getHeight();
    }
    float ax = 0, ay = 0;
    float dx = 0, dy = 0;
    double t = DrawingUtilities.calculateRotationAngle(px1, py1, px2, py2);
    double e = 0.3; // DrawingUtilities.calculateRotationAngle(0, 0, tw, th);

    if (t > Math.PI / 2 && t < Math.PI * 3 / 2) {
      if (t > Math.PI) {
	t -= Math.PI;
      } else {
	t += Math.PI;
      }
    }

    if (debug) {
      System.out.println("label=" + (label != null ? label.getText() : "null") + 
			 "  tw=" + tw + "  th=" + th + "  t=" + t);
    }

    switch (edgeLayoutPolicy) {
    case EDGE_LABEL_DEFAULT_LAYOUT:
      if (label != null) {
	label.setRotationAngle(0);
      }
      switch (role) {
      case ROLE_EDGE_LABEL:
	dx = 0;
	dy = -th / 2 - ypad;
	break;
      case ROLE_EDGE_HEAD_LABEL:
	dx = (px2 <= px1 ? tw / 2 + th / 2 : -tw / 2 - th / 2);
	dy = -th / 2 - ypad;
	break;
      case ROLE_EDGE_HEAD_LABEL2:
	dx = (px2 <= px1 ? tw / 2 + th / 2 : -tw / 2 - th / 2);
	dy = th / 2;
	break;
      case ROLE_EDGE_TAIL_LABEL:
	dx = (px1 <= px2 ? tw / 2 + th / 2 : -tw / 2 - th / 2);
	dy = -th / 2 - ypad;
	break;
      case ROLE_EDGE_TAIL_LABEL2:
	dx = (px1 <= px2 ? tw / 2 + th / 2 : -tw / 2 - th / 2);
	dy = +th / 2;
	break;
      }
      break;

    case EDGE_LABEL_HORIZONTAL_LAYOUT:
      if (label != null) {
	label.setRotationAngle(0);
      }
      if (role == ROLE_EDGE_LABEL) {
	if (t <= e || t <= Math.PI + e && t >= Math.PI - e
	    || t >= Math.PI * 2 - e) {
	  dx = 0;
	  dy = -th;
	} else if (t > 0 && t <= Math.PI * 0.5 || t > Math.PI
	    && t < Math.PI * 1.5) {
	  dx = tw / 2 + th;
	  dy = 0;
	} else {
	  dx = -tw / 2 - th;
	  dy = 0;
	}
	// break;
      } else {
	if (t <= e || t <= Math.PI + e && t >= Math.PI - e
	    || t >= Math.PI * 2 - e) {
	  switch (role) {
	  case ROLE_EDGE_HEAD_LABEL:
	    dx = (px2 <= px1 ? tw / 2 + th : -tw / 2 - th);
	    dy = -th;
	    break;
	  case ROLE_EDGE_HEAD_LABEL2:
	    dx = (px2 <= px1 ? tw / 2 + th : -tw / 2 - th);
	    dy = th;
	    break;
	  case ROLE_EDGE_TAIL_LABEL:
	    dx = (px1 <= px2 ? tw / 2 + th : -tw / 2 - th);
	    dy = -th;
	    break;
	  case ROLE_EDGE_TAIL_LABEL2:
	    dx = (px1 <= px2 ? tw / 2 + th : -tw / 2 - th);
	    dy = +th;
	    break;
	  default:
	  }
	} else if (t <= Math.PI / 2 + e && t >= Math.PI / 2 - e
	    || t <= Math.PI * 3 / 2 + e && t >= Math.PI * 3 / 2 - e) {
	  switch (role) {
	  case ROLE_EDGE_HEAD_LABEL:
	    dx = tw / 2 + th;
	    dy = (py2 <= py1 ? th : -th);
	    break;
	  case ROLE_EDGE_HEAD_LABEL2:
	    dx = -tw / 2 - th;
	    dy = (py2 <= py1 ? th : -th);
	    break;
	  case ROLE_EDGE_TAIL_LABEL:
	    dx = tw / 2 + th;
	    dy = (py2 <= py1 ? -th : th);
	    break;
	  case ROLE_EDGE_TAIL_LABEL2:
	    dx = -tw / 2 - th;
	    dy = (py2 <= py1 ? -th : th);
	    break;
	  default:
	  }
	} else {
	  switch (role) {
	  case ROLE_EDGE_HEAD_LABEL:
	    ax = (px2 <= px1 ? tw / 2 + th : -tw / 2 - th);
	    ay = -th;
	    break;
	  case ROLE_EDGE_HEAD_LABEL2:
	    ax = (px2 <= px1 ? tw / 2 + th : -tw / 2 - th);
	    ay = th;
	    break;
	  case ROLE_EDGE_TAIL_LABEL:
	    ax = (px1 <= px2 ? tw / 2 + th : -tw / 2 - th);
	    ay = -th;
	    break;
	  case ROLE_EDGE_TAIL_LABEL2:
	    ax = (px1 <= px2 ? tw / 2 + th : -tw / 2 - th);
	    ay = th;
	    break;
	  default:
	  }
	  float sina = (float) Math.sin(t);
	  float cosa = (float) Math.cos(t);
	  dx = -ay * sina + ax * cosa;
	  dy = ay * cosa + ax * sina;
	}

      }
      break;

    case EDGE_LABEL_PARALLEL_LAYOUT:
      if (label != null) {
	label.setRotationAngle((float) t);
      }
      switch (role) {
      case ROLE_EDGE_LABEL:
	ax = 0;
	ay = -th / 2;
	break;
      case ROLE_EDGE_HEAD_LABEL:
	ax = (px2 <= px1 ? tw / 2 + th / 2 : -tw / 2 - th / 2);
	ay = -th / 2;
	break;
      case ROLE_EDGE_HEAD_LABEL2:
	ax = (px2 <= px1 ? tw / 2 + th / 2 : -tw / 2 - th / 2);
	ay = th / 2;
	break;
      case ROLE_EDGE_TAIL_LABEL:
	ax = (px1 <= px2 ? tw / 2 + th / 2 : -tw / 2 - th / 2);
	ay = -th / 2;
	break;
      case ROLE_EDGE_TAIL_LABEL2:
	ax = (px1 <= px2 ? tw / 2 + th / 2 : -tw / 2 - th / 2);
	ay = th / 2;
	break;
      }
      float sina = (float) Math.sin(t);
      float cosa = (float) Math.cos(t);
      dx = -ay * sina + ax * cosa;
      dy = ay * cosa + ax * sina;
      break;
    }
    return new Offset(dx, dy);
  }

  @Override
  public void positionLabel(int role) {
    Label label = findLabel(role);
    if (label != null) {

      if (debug) {
	System.out.println("GraphEdgeBase.positionLabel() role=" + role);
      }

      float px1 = (float) p1.getX();
      float py1 = (float) p1.getY();
      float px2 = (float) p2.getX();
      float py2 = (float) p2.getY();
      float x = Math.min(px1, px2);
      float y = Math.min(py1, py2);
      float w = Math.abs(px2 - px1);
      float h = Math.abs(py2 - py1);
      float tw = label.getWidth();
      float th = label.getHeight();
      Offset offset = calculateLabelOffset(role);
      float dx = 0;
      float dy = 0;
      if (offset != null) {
	dx = offset.dx;
	dy = offset.dy;
      }
      float ldx = 0;
      float ldy = 0;
      Offset labelOffset = label.getOffset();
      if (labelOffset != null) {
	ldx = labelOffset.dx;
	ldy = labelOffset.dy;
      }
      switch (role) {
      case ROLE_EDGE_LABEL:
	label.setPosition(x + (w - tw) / 2 + dx + ldx, 
			  y + (h - th) / 2 + dy + ldy);
	break;

      case ROLE_EDGE_HEAD_LABEL:
      case ROLE_EDGE_HEAD_LABEL2:
	label.setPosition(px2 - tw / 2 + dx + ldx, 
			  py2 - th / 2 + dy + ldy);
	break;

      case ROLE_EDGE_TAIL_LABEL:
      case ROLE_EDGE_TAIL_LABEL2:
	label.setPosition(px1 - tw / 2 + dx + ldx, 
			  py1 - th / 2 + dy + ldy);
      default:
      }
    }
  }

  public boolean isShowHotSpot() {
    return showHotSpot;
  }

  public void setShowHotSpot(boolean showHotSpot) {
    this.showHotSpot = showHotSpot;
  }

  @Override
  protected void drawHotSpots(Graphics2D g2) {
    if (showHotSpot) {
      float x1 = (float) p1.getX();
      float y1 = (float) p1.getY();
      float x2 = (float) p2.getX();
      float y2 = (float) p2.getY();
      float cx = (x1 + x2) / 2;
      float cy = (y1 + y2) / 2;
      int n = EDGE_LABEL_ROLES.length;
      for (int i = 0; i < n; i++) {
	int role = EDGE_LABEL_ROLES[i];
	Offset offset = calculateLabelOffset(role, null);
	switch (role) {
	case ROLE_EDGE_LABEL:
	  g2.fill(new Ellipse2D.Float(cx - HOTSPOT_SIZE, cy - HOTSPOT_SIZE,
	      HOTSPOT_SIZE * 2, HOTSPOT_SIZE * 2));
	  break;
	case ROLE_EDGE_HEAD_LABEL:
	  // case ROLE_EDGE_HEAD_LABEL2:
	  g2.fill(new Ellipse2D.Float(x2 + offset.dx - HOTSPOT_SIZE, y2
	      + offset.dy - HOTSPOT_SIZE, HOTSPOT_SIZE * 2, HOTSPOT_SIZE * 2));
	  break;
	case ROLE_EDGE_TAIL_LABEL:
	  // case ROLE_EDGE_TAIL_LABEL2:
	  g2.fill(new Ellipse2D.Float(x1 + offset.dx - HOTSPOT_SIZE, y1
	      + offset.dy - HOTSPOT_SIZE, HOTSPOT_SIZE * 2, HOTSPOT_SIZE * 2));
	  break;
	default:
	}
      }
    }
  }

  @Override
  public int isOnHotSpot(float px, float py) {
    if (acceptLabel) {
      float x1 = (float) p1.getX();
      float y1 = (float) p1.getY();
      float x2 = (float) p2.getX();
      float y2 = (float) p2.getY();
      float cx = (x1 + x2) / 2;
      float cy = (y1 + y2) / 2;
      /*
       * if (isNearHotSpot(px, py, cx, cy)) { return HOTSPOT_EDGE_LABEL; }
       * else if (isNearHotSpot(px, py, x1, y1)) { return
       * HOTSPOT_EDGE_TAIL_LABEL; } else if (isNearHotSpot(px, py, x2, y2)) {
       * return HOTSPOT_EDGE_HEAD_LABEL; }
       */
      int n = EDGE_LABEL_ROLES.length;
      for (int i = 0; i < n; i++) {
	int role = EDGE_LABEL_ROLES[i];
	Offset offset = calculateLabelOffset(role, null);
	switch (role) {
	case ROLE_EDGE_LABEL:
	  if (isNearHotSpot(px, py, cx, cy)) {
	    return HOTSPOT_EDGE_LABEL;
	  }
	case ROLE_EDGE_HEAD_LABEL:
	  if (isNearHotSpot(px, py, x2 + offset.dx, y2 + offset.dy)) {
	    return HOTSPOT_EDGE_HEAD_LABEL;
	  }
	  // case HEAD_LABEL2:
	  // if (isNearHotSpot(px, py, x2 + offset.dx, y2 +
	  // offset.dy)) {
	  // return HOTSPOT_EDGE_HEAD_LABEL2;
	  // }
	case ROLE_EDGE_TAIL_LABEL:
	  if (isNearHotSpot(px, py, x1 + offset.dx, y1 + offset.dy)) {
	    return HOTSPOT_EDGE_TAIL_LABEL;
	  }
	  // case TAIL_LABEL2:
	  // if (isNearHotSpot(px, py, x1 + offset.dx, y1 +
	  // offset.dy)) {
	  // return HOTSPOT_EDGE_TAIL_LABEL2;
	  // }
	default:
	}
      }
    }
    return HOTSPOT_NONE;
  }

  public void adjustControlPoints() {
  }

  public void adjustLabels() {
    if (debug) {
      System.out.println("GraphEdgeBase.adjustLabels()");
    }

    int n = EDGE_LABEL_ROLES.length;
    for (int i = 0; i < n; i++) {
      int role = EDGE_LABEL_ROLES[i];
      positionLabel(role);
    }
    calculateBounds();
  }

  @Override
  public void setEnds(float x1, float y1, float x2, float y2) {
    super.setEnds(x1, y1, x2, y2);
    adjustLabels();
  }

  @Override
  public void setLabelOffset(int role) {
    if (debug) {
      System.out.println("GraphEdgeBase.setLabelOffset(role)");
    }

    Label label = findLabel(role);
    if (label != null) {
      float px1 = (float) p1.getX();
      float py1 = (float) p1.getY();
      float px2 = (float) p2.getX();
      float py2 = (float) p2.getY();
      float x = Math.min(px1, px2);
      float y = Math.min(py1, py2);
      float w = Math.abs(px2 - px1);
      float h = Math.abs(py2 - py1);
      Point2D pos = label.getPosition();
      float tx = (float) pos.getX();
      float ty = (float) pos.getY();
      float tw = label.getWidth();
      float th = label.getHeight();
      float labelOffsetX = 0f;
      float labelOffsetY = 0f;
      Offset offset = calculateLabelOffset(role);
      switch (role) {
      case ROLE_EDGE_LABEL:
	labelOffsetX = tx - (x + (w - tw) / 2 + offset.dx);
	labelOffsetY = ty - (y + (h - th) / 2 + offset.dy);
	break;

      case ROLE_EDGE_HEAD_LABEL:
      case ROLE_EDGE_HEAD_LABEL2:
	labelOffsetX = tx - (px2 - tw / 2 + offset.dx);
	labelOffsetY = ty - (py2 - th / 2 + offset.dy);
	break;

      case ROLE_EDGE_TAIL_LABEL:
      case ROLE_EDGE_TAIL_LABEL2:
	labelOffsetX = tx - (px1 - tw / 2 + offset.dx);
	labelOffsetY = ty - (py1 - th / 2 + offset.dy);
      default:
      }
      label.setOffset(labelOffsetX, labelOffsetY);
    }
  }

  public void setLabelOffset(int role, float dx, float dy) {
    if (debug) {
      System.out.println("GraphEdgeBase.setLabelOffset(role,dx,dy)");
    }

    Label label = findLabel(role);
    if (label != null) {
      label.setOffset(dx, dy);
    }
  }

  public void adjustHeadOffset() { 
    adjustHeadOffset(getEnd2(), getDestination());
  }

  public void adjustHeadOffset(Point2D p, GraphNodeBase node) { 
    if (debug) 
      System.out.println("GraphEdgeBase.adjustHeadOffset() (p!=null): " + (p!=null) + 
			 " node!=null: " + (node!=null));

    if (p != null && node != null) {       
      Point2D c = node.getCenter();
      float px = (float) p.getX();
      float py = (float) p.getY();
      float cx = (float) c.getX();
      float cy = (float) c.getY();
      float w = node.getShapeWidth() / 2;
      float h = node.getShapeHeight() / 2;
      float d = 5; 

      float dx = 0;
      float dy = 0;
      if (w > d) { 
	if (px > (cx + w - d)) { 
	  px = cx + w - d;
	}
	if (px < (cx - w + d)) { 
	  px = cx - w + d;
	}
      } 
      if (h > d) { 
	if (py > (cy + h - d)) { 
	  py = cy + h - d;
	}
	if (py < (cy - h + d)) { 
	  py = cy - h + d;
	}
      }
      dx = (px - cx) / (2 * w);
      dy = (py - cy) / (2 * h);
      headOffset.set(dx, dy);

      if (debug) 
	System.out.println("GraphEdgeBase.adjustHeadOffset() w=" + w + " h=" + h + 
			   " p=" + p + " c=" + c + " dx=" + dx + " dy=" + dy);
    } else { 
      headOffset.set(0, 0);
    }
  }

  public void adjustTailOffset() { 
    adjustTailOffset(getEnd1(), getSource());
  }

  public void adjustTailOffset(Point2D p, GraphNodeBase node) { 
    if (debug) 
      System.out.println("GraphEdgeBase.adjustTailOffset() (p!=null): " + (p!=null) + 
			 " node!=null: " + (node!=null));

    if (p != null && node != null) { 
      Point2D c = node.getCenter();
      float px = (float) p.getX();
      float py = (float) p.getY();
      float cx = (float) c.getX();
      float cy = (float) c.getY();
      float w = node.getShapeWidth() / 2;
      float h = node.getShapeHeight() / 2;
      float d = 5; 

      float dx = 0;
      float dy = 0;
      if (w > d) { 
	if (px > (cx + w - d)) { 
	  px = cx + w - d;
	}
	if (px < (cx - w + d)) { 
	  px = cx - w + d;
	}
      } 
      if (h > d) { 
	if (py > (cy + h - d)) { 
	  py = cy + h - d;
	}
	if (py < (cy - h + d)) { 
	  py = cy - h + d;
	}
      }
      dx = (px - cx) / (2 * w);
      dy = (py - cy) / (2 * h);
      tailOffset.set(dx, dy);

      if (debug) 
	System.out.println("GraphEdgeBase.adjustTailOffset() w=" + w + " h=" + h + 
			   " p=" + p + " c=" + c + " dx=" + dx + " dy=" + dy);
    } else { 
      tailOffset.set(0, 0);
    }
  }

  public void setHeadOffset(float dx, float dy) {
    if (dx > 0.5f) {
      dx = 0.5f;
    } else if (dx < -0.5f) {
      dx = -0.5f;
    }
    if (dy > 0.5f) {
      dy = 0.5f;
    } else if (dy < -0.5f) {
      dy = -0.5f;
    }
    headOffset.set(dx, dy);
  }

  public void setTailOffset(float dx, float dy) {
    if (dx > 0.5f) {
      dx = 0.5f;
    } else if (dx < -0.5f) {
      dx = -0.5f;
    }
    if (dy > 0.5f) {
      dy = 0.5f;
    } else if (dy < -0.5f) {
      dy = -0.5f;
    }
    tailOffset.set(dx, dy);
  }

  public Offset getHeadOffset() { 
    return headOffset;
  }

  public Offset getTailOffset() { 
    return tailOffset;
  }

  public void swapEnds() { 
    //Offset offset = headOffset;
    //headOffset = tailOffset;
    //tailOffset = offset; 

    float x1 = (float) p1.getX();
    float y1 = (float) p1.getY();
    float x2 = (float) p2.getX();
    float y2 = (float) p2.getY();
    p1.setLocation(x2, y2);
    p2.setLocation(x1, y1);


  }

  public void setEdgeLayoutPolicy(int p) {
    edgeLayoutPolicy = p;
  }

  public int getEdgeLayoutPolicy() {
    return edgeLayoutPolicy;
  }

  @Override
  public Shape getOutlineShape() {
    return new Line2D.Float(p1, p2);
  }

  @Override
  public String toString() {
    if (comp != null) {
      GraphNodeBase src = getSource();
      GraphNodeBase des = getDestination();
      return ("(" + src + "," + des + ")");
    } else {
      return super.toString();
    }
  }

  public String detailString() { 
    return ("" + getClass().getName() + " p1=" + p1 +  " p2=" + p2 + 
	    " headOffset=" + headOffset + " tailOffset=" + tailOffset);
  }

  @Override
  protected void writeXMLElements(CodeWriter out, int mode,
      String namespacePrefix) {
    if (out != null) {
      super.writeXMLElements(out, mode, namespacePrefix);

      String nodename;
      nodename = getPrefixedName("GraphEdge", namespacePrefix);
      out.println("<" + nodename + ' ' + 
		  getXMLAttributeString("label", acceptLabel) + ' ' + 
		  getXMLAttributeString("selfloop", allowSelfLoop) + ' ' + 
		  getXMLAttributeString("dangle", allowDangling) + ' ' + 
		  getXMLAttributeString("layout", edgeLayoutPolicy) + ' ' + 
		  getXMLAttributeString("hotspot", showHotSpot) + "/>");
      
      nodename = getPrefixedName("Offset", namespacePrefix);
      if (headOffset != null) {
	out.println("<" + nodename + ' ' + getXMLAttributeString("pos", "head") + ' ' + 
		    getXMLAttributeString("dx", headOffset.dx) + ' ' + 
		    getXMLAttributeString("dy", headOffset.dy) + "/>");
      }
      if (tailOffset != null) {
	out.println("<" + nodename + ' ' + getXMLAttributeString("pos", "tail") + ' ' + 
		    getXMLAttributeString("dx", tailOffset.dx) + ' ' + 
		    getXMLAttributeString("dy", tailOffset.dy) + "/>");
      }
    }
  }

  @Override
  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
    if (e != null) {
      String nodename = e.getLocalName();
      if ("GraphEdge".equals(nodename)) {
	String s = e.getAttribute("label");
	acceptLabel = Boolean.valueOf(s);
	s = e.getAttribute("selfloop");
	if (s != null) { 
	  allowSelfLoop = Boolean.valueOf(s);
	}
	s = e.getAttribute("dangle");
	if (s != null) { 
	  allowDangling = Boolean.valueOf(s);
	}

	String ls, hs;
	ls = e.getAttribute("layout");
	hs = e.getAttribute("hotspot");
	try {
	  edgeLayoutPolicy = Integer.parseInt(ls);
	} catch (NumberFormatException ex) {
	}
	showHotSpot = Boolean.valueOf(hs);
      } else if ("Offset".equals(nodename)) {
	String pos, dxs, dys;
	pos = e.getAttribute("pos");
	dxs = e.getAttribute("dx");
	dys = e.getAttribute("dy");
	float dx = 0, dy = 0;
	try {
	  dx = Float.parseFloat(dxs);
	  dy = Float.parseFloat(dys);
	  if ("head".equals(pos)) {
	    if (headOffset == null) {
	      headOffset = new Offset(dx, dy);
	    } else {
	      headOffset.set(dx, dy);
	    }
	  } else {
	    if (tailOffset == null) {
	      tailOffset = new Offset(dx, dy);
	    } else {
	      tailOffset.set(dx, dy);
	    }
	  }
	} catch (NumberFormatException ex) {
	}
      } else {
	super.processXMLElement(e, namespaceURI);
      }
    }
  }

  public void printEdge(PrintWriter out) {
  }

  // edge label layout policy
  public static final int EDGE_LABEL_DEFAULT_LAYOUT = 0;

  public static final int EDGE_LABEL_HORIZONTAL_LAYOUT = 1;

  public static final int EDGE_LABEL_PARALLEL_LAYOUT = 2;

  // the offset is proportional to the width and height of the node and
  // relative to the center of the node. Values are in [-0.5, 0.5]
  protected Offset headOffset = new Offset();

  protected Offset tailOffset = new Offset();

  protected boolean allowSelfLoop = false;
  protected boolean allowDangling = true;

  protected boolean showHotSpot = false;

  protected int edgeLayoutPolicy = EDGE_LABEL_DEFAULT_LAYOUT;
  // protected int edgeLayoutPolicy = EDGE_LABEL_HORIZONTAL_LAYOUT;
  // protected int edgeLayoutPolicy = EDGE_LABEL_PARALLEL_LAYOUT;

  protected static final boolean debug = false;

}
